package io.github.biezhi.elves.ok.http;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.apache.commons.io.IOUtils;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * OkHttp3 工具类
 * <pre>
 *     // 通过 OkHttpClient Builder 初始化实例
 *     OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
 *     // 自定义参数配置
 *     builder.addInterceptor(interceptor);
 *     builder.connectTimeout(connectTimeout, TimeUnit.SECONDS);
 *     builder.readTimeout(readTimeout, TimeUnit.SECONDS);
 *     builder.writeTimeout(writeTimeout, TimeUnit.SECONDS);
 *
 *     //  默认值配置（优先级高），如果配置了这里的值，则builder初始化配置的值将被覆盖，如果builder不配置，则使用默认值。
 *     connectTimeout：链接超时时间，单位：s，默认：5
 *     readTimeout：读取响应超时时间，单位：s，默认：8
 *     writeTimeout：写入数据超时时间，单位：s，默认：8
 *     charset：编码格式，默认：UTF-8
 * </pre>
 *
 * @author Created by YL on 2017/6/13.
 */
public class OkClient {
    private final OkHttpClient.Builder builder;
    private final int connectTimeout;
    private final int readTimeout;
    private final int writeTimeout;

    private final String charset;

    private static OkHttpClient client;

    OkClient(OkHttpClient.Builder builder, int connectTimeout, int readTimeout, int writeTimeout, String
            charset) {
        super();
        this.builder = builder;
        this.connectTimeout = connectTimeout;
        this.readTimeout = readTimeout;
        this.writeTimeout = writeTimeout;
        this.charset = charset;

        if (this.builder == null)
            throw new NullPointerException("builder == null");
        builder.connectTimeout(this.connectTimeout, TimeUnit.SECONDS);
        builder.readTimeout(this.readTimeout, TimeUnit.SECONDS);
        builder.writeTimeout(this.writeTimeout, TimeUnit.SECONDS);
        client = builder.build();
    }

    public OkHttpClient.Builder getBuilder() {
        return builder;
    }

    public int getConnectTimeout() {
        return connectTimeout;
    }

    public int getReadTimeout() {
        return readTimeout;
    }

    public int getWriteTimeout() {
        return writeTimeout;
    }

    public String getCharset() {
        return charset;
    }

    public OkClient.Builder copy(final OkClient.Builder copy) {
        return new OkClient.Builder(copy.builder).connectTimeout(copy.connectTimeout).readTimeout(copy.readTimeout)
                .writeTimeout(copy.writeTimeout).charset(copy.charset);
    }
    
    public static class Builder {
        private OkHttpClient.Builder builder;
        private int connectTimeout = 5; // 链接超时时间，单位：s
        private int readTimeout = 8; // 读取响应超时时间，单位：s
        private int writeTimeout = 8; // 写入数据超时时间，单位：s
        private String charset = "UTF-8"; // 编码格式

        public Builder(OkHttpClient.Builder builder) {
            this.builder = builder;
        }

        public Builder connectTimeout(int connectTimeout) {
            this.connectTimeout = connectTimeout;
            return this;
        }

        public Builder readTimeout(int readTimeout) {
            this.readTimeout = readTimeout;
            return this;
        }

        public Builder writeTimeout(int writeTimeout) {
            this.writeTimeout = writeTimeout;
            return this;
        }

        public Builder charset(String charset) {
            this.charset = charset;
            return this;
        }

        public OkClient build() {
            return new OkClient(builder, connectTimeout, readTimeout, writeTimeout, charset);
        }
    }

    /**
     * application/json; charset=utf-8
     */
    private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    /**
     * 表单提交：application/x-www-form-urlencoded; charset=utf-8
     */
    private static final MediaType FORM = MediaType.parse("application/x-www-form-urlencoded; charset=utf-8");
    /**
     * 文件上传：application/octet-stream
     */
    private static final MediaType FILE = MediaType.parse("application/octet-stream");
    
    /**
     * 发送 GET 请求
     *
     * @param url 请求地址
     */
    public OkResponse get(String url) throws Exception {
        Request request = new Request.Builder().url(url).build();
        return execute(request);
    }

    /**
     * 发送 POST REQUEST BODY 请求
     *
     * @param url  请求地址
     * @param json execute body
     */
    public OkResponse post(String url, String json) throws Exception {
        RequestBody body = RequestBody.create(JSON, json);
        Request request = new Request.Builder().url(url).post(body).build();
        return execute(request);
    }

    /**
     * 发送 POST 请求，模拟表单提交
     *
     * @param url 请求地址
     * @param kv  表单提交的key-value，key1=value1&key2=value2&key3=value3
     */
    public OkResponse post(String url, Map<String, String> kv) throws Exception {
        StringBuilder str = new StringBuilder(32);
        if (kv != null && !kv.isEmpty()) {
            String sp = "";
            for (Map.Entry<String, String> entry : kv.entrySet()) {
                String key = entry.getKey();
                String val = entry.getValue();
                str.append(key).append("=").append(val).append(sp);
                sp = "&";
            }
        }
        RequestBody body = RequestBody.create(FORM, str.toString());
        Request request = new Request.Builder().url(url).post(body).build();
        return execute(request);
    }

    /**
     * 发送 POST 请求，模拟表单上传文件
     *
     * @param url    请求地址
     * @param file   上传文件
     * @param params 提交参数
     */
    public OkResponse post(String url, File file, Map<String, String> params) throws Exception {
        RequestBody body = RequestBody.create(FILE, file);
        MultipartBody.Builder files = new MultipartBody.Builder().setType(MultipartBody.FORM)
                // 请求key ，文件名称 ， RequestBody
                .addFormDataPart("files", file.getName(), body);
        if (params != null) {
            // params 里面是请求中所需要的 key 和 value
            for (Map.Entry<String, String> entry : params.entrySet()) {
                files.addFormDataPart(entry.getKey(), entry.getValue());
            }
        }
        RequestBody multipartBody = files.build();
        Request request = new Request.Builder().url(url).post(multipartBody).build();
        return execute(request);
    }

    /**
     * 发送请求
     *
     * @param request 请求对象
     */
    public OkResponse execute(Request request) throws Exception {
        try (Response response = client.newCall(request).execute()) {
            if (response == null) {
                throw new Exception("return response is null");
            }
            ResponseBody body = response.body();
            if (body == null) {
                throw new Exception("return response.body is null");
            }
            int code = response.code();
            //            System.out.println("isSuccessful ---> " + response.isSuccessful());
            //            System.out.println("code ---> " + code);
            //            if (code != 200) {
            //                throw new CoreException(getCause(code), code);
            //            }
            String message = getCause(code);
            // 使用 string()，当数据大于 1MB 时，容易造成内存溢出。推荐使用 byteStream()，记得关闭操作流
            // String string = body.string();
            String string = IOUtils.toString(body.byteStream(), this.getCharset());
            OkResponse okResponse = new OkResponse();
            okResponse.setCode(code);
            okResponse.setMessage(message);
            okResponse.setBody(string);
            return okResponse;
        } catch (Exception e) {
            throw new Exception(e);
        }
    }

    private static final Map<Integer, String> http_status = new HashMap<Integer, String>();

    static {
        // --- 1xx Informational ---
        /** {@code 100 Continue} (HTTP/1.1 - RFC 2616) */
        http_status.put(100, "Continue");
        /** {@code 101 Switching Protocols} (HTTP/1.1 - RFC 2616)*/
        http_status.put(101, "Switching Protocols");
        /** {@code 102 Processing} (WebDAV - RFC 2518) */
        http_status.put(102, "Processing");

        //        // --- 2xx Success ---
        //        /** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
        //        http_status.put(200, "OK");
        //        /** {@code 201 Created} (HTTP/1.0 - RFC 1945) */
        //        http_status.put(201, "Created");
        //        /** {@code 202 Accepted} (HTTP/1.0 - RFC 1945) */
        //        http_status.put(202, "Accepted");
        //        /** {@code 203 Non Authoritative Information} (HTTP/1.1 - RFC 2616) */
        //        http_status.put(203, "Non Authoritative Information");
        //        /** {@code 204 No Content} (HTTP/1.0 - RFC 1945) */
        //        http_status.put(204, "No Content");
        //        /** {@code 205 Reset Content} (HTTP/1.1 - RFC 2616) */
        //        http_status.put(205, "Reset Content");
        //        /** {@code 206 Partial Content} (HTTP/1.1 - RFC 2616) */
        //        http_status.put(206, "Partial Content");
        //        /**
        //         * {@code 207 Multi-Status} (WebDAV - RFC 2518)
        //         * or
        //         * {@code 207 Partial Update OK} (HTTP/1.1 - draft-ietf-http-v11-spec-rev-01?)
        //         */
        //        http_status.put(207, "Partial Update OK");

        // --- 3xx Redirection ---

        /** {@code 300 Mutliple Choices} (HTTP/1.1 - RFC 2616) */
        http_status.put(300, "Mutliple Choices");
        /** {@code 301 Moved Permanently} (HTTP/1.0 - RFC 1945) */
        http_status.put(301, "Moved Permanently");
        /** {@code 302 Moved Temporarily} (Sometimes {@code Found}) (HTTP/1.0 - RFC 1945) */
        http_status.put(302, "Moved Temporarily");
        /** {@code 303 See Other} (HTTP/1.1 - RFC 2616) */
        http_status.put(303, "See Other");
        /** {@code 304 Not Modified} (HTTP/1.0 - RFC 1945) */
        http_status.put(304, "Not Modified");
        /** {@code 305 Use Proxy} (HTTP/1.1 - RFC 2616) */
        http_status.put(305, "Use Proxy");
        /** {@code 307 Temporary Redirect} (HTTP/1.1 - RFC 2616) */
        http_status.put(307, "Temporary Redirect");

        // --- 4xx Client Error ---

        /** {@code 400 Bad Request} (HTTP/1.1 - RFC 2616) */
        http_status.put(400, "Bad Request");
        /** {@code 401 Unauthorized} (HTTP/1.0 - RFC 1945) */
        http_status.put(401, "Unauthorized");
        /** {@code 402 Payment Required} (HTTP/1.1 - RFC 2616) */
        http_status.put(402, "Payment Required");
        /** {@code 403 Forbidden} (HTTP/1.0 - RFC 1945) */
        http_status.put(403, "Forbidden");
        /** {@code 404 Not Found} (HTTP/1.0 - RFC 1945) */
        http_status.put(404, "Not Found");
        /** {@code 405 Method Not Allowed} (HTTP/1.1 - RFC 2616) */
        http_status.put(405, "Method Not Allowed");
        /** {@code 406 Not Acceptable} (HTTP/1.1 - RFC 2616) */
        http_status.put(406, "Not Acceptable");
        /** {@code 407 Proxy Authentication Required} (HTTP/1.1 - RFC 2616)*/
        http_status.put(407, "Proxy Authentication Required");
        /** {@code 408 Request Timeout} (HTTP/1.1 - RFC 2616) */
        http_status.put(408, "Request Timeout");
        /** {@code 409 Conflict} (HTTP/1.1 - RFC 2616) */
        http_status.put(409, "Conflict");
        /** {@code 410 Gone} (HTTP/1.1 - RFC 2616) */
        http_status.put(410, "Gone");
        /** {@code 411 Length Required} (HTTP/1.1 - RFC 2616) */
        http_status.put(411, "Length Required");
        /** {@code 412 Precondition Failed} (HTTP/1.1 - RFC 2616) */
        http_status.put(412, "Precondition Failed");
        /** {@code 413 Request Entity Too Large} (HTTP/1.1 - RFC 2616) */
        http_status.put(413, "Request Entity Too Large");
        /** {@code 414 Request-URI Too Long} (HTTP/1.1 - RFC 2616) */
        http_status.put(414, "Request-URI Too Long");
        /** {@code 415 Unsupported Media Type} (HTTP/1.1 - RFC 2616) */
        http_status.put(415, "Unsupported Media Type");
        /** {@code 416 Requested Range Not Satisfiable} (HTTP/1.1 - RFC 2616) */
        http_status.put(416, "Requested Range Not Satisfiable");
        /** {@code 417 Expectation Failed} (HTTP/1.1 - RFC 2616) */
        http_status.put(417, "Expectation Failed");

        /**
         * Static constant for a 418 error.
         * {@code 418 Unprocessable Entity} (WebDAV drafts?)
         * or {@code 418 Reauthentication Required} (HTTP/1.1 drafts?)
         */
        // not used
        // http_status.put(418, "");

        /**
         * Static constant for a 419 error.
         * {@code 419 Insufficient Space on Resource}
         * (WebDAV - draft-ietf-webdav-protocol-05?)
         * or {@code 419 Proxy Reauthentication Required}
         * (HTTP/1.1 drafts?)
         */
        http_status.put(419, "Proxy Reauthentication Required");
        /**
         * Static constant for a 420 error.
         * {@code 420 Method Failure}
         * (WebDAV - draft-ietf-webdav-protocol-05?)
         */
        http_status.put(420, "Method Failure");
        /** {@code 422 Unprocessable Entity} (WebDAV - RFC 2518) */
        http_status.put(422, "Unprocessable Entity");
        /** {@code 423 Locked} (WebDAV - RFC 2518) */
        http_status.put(423, "Locked");
        /** {@code 424 Failed Dependency} (WebDAV - RFC 2518) */
        http_status.put(424, "Failed Dependency");

        // --- 5xx Server Error ---
        /** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
        http_status.put(500, "Server Error");
        /** {@code 501 Not Implemented} (HTTP/1.0 - RFC 1945) */
        http_status.put(501, "Not Implemented");
        /** {@code 502 Bad Gateway} (HTTP/1.0 - RFC 1945) */
        http_status.put(502, "Bad Gateway");
        /** {@code 503 Service Unavailable} (HTTP/1.0 - RFC 1945) */
        http_status.put(503, "Service Unavailable");
        /** {@code 504 Gateway Timeout} (HTTP/1.1 - RFC 2616) */
        http_status.put(504, "Gateway Timeout");
        /** {@code 505 HTTP Version Not Supported} (HTTP/1.1 - RFC 2616) */
        http_status.put(505, "HTTP Version Not Supported");
        /** {@code 507 Insufficient Storage} (WebDAV - RFC 2518) */
        http_status.put(507, "Insufficient Storage");
    }

    /**
     * 通过statusCode获取错误信息
     *
     * @author YangLi : 2015年12月16日
     */
    private static String getCause(int statusCode) {
        if (statusCode == 200) {
            return "ok";
        }
        String str = http_status.get(statusCode);
        if (str != null && str.trim().equals(""))
            return "http-" + statusCode + " " + str;
        else
            return "http-" + statusCode + " Unknown Error";
    }
}
